using System;
using System.Text;
using System.Collections;
using System.Globalization;
using System.Xml;
using System.Xml.Serialization;
using System.Security.Permissions;
#if !PocketPC || DesignTime
using System.Runtime.Serialization;
using System.ComponentModel;
#endif

namespace GeoFramework.IO
{
	/// <summary>Represents an arbitrary packet of GPS data.</summary>
#if !PocketPC
    [Serializable()]
	[TypeConverter(typeof(ExpandableObjectConverter))]
#endif
    public class Packet : ArrayList, IFormattable, IXmlSerializable
#if !PocketPC || DesignTime
, ISerializable
#endif
	{
        private DateTime pTimeStamp = DateTime.Now;

		#region Constructors
	
		/// <summary>Creates a new instance.</summary>
		public Packet() 
			: base()
		{
		}

		/// <summary>Creates a new instance with the specified initial capacity.</summary>
		public Packet(int capacity) 
			: base(capacity)
		{
		}
		

		/// <summary>Creates a new instance containing the specified bytes.</summary>
		/// <param name="bytes">An array of bytes to store as the packet.</param>
		public Packet(byte[] bytes)
			: base(bytes.Length)
		{
			AddRange(bytes);
		}

#if !PocketPC || DesignTime
        /// <summary>
        /// Creates a new instance by de-serializing previously-serialized information.
        /// </summary>
        /// <param name="info">A <strong>SerializationInfo</strong> object containing serialized data.</param>
        /// <param name="context">A <strong>StreamingContext</strong> object describing the context of serialization.</param>
        /// <remarks>This constructor will de-serialize a previously-serialized object.  Objects are created
        /// serialized and de-serialized based on the "DecimalDegrees" value to maintain precision.</remarks>
        protected Packet(SerializationInfo info, StreamingContext context)
        {
            AddBytes((byte[])info.GetValue("Bytes", typeof(byte[])));
        }
#endif

		#endregion

		#region Public Members

        /// <summary>
        /// Returns the local date and time that the packet was created.
        /// </summary>
        public DateTime TimeStamp
        {
            get
            {
                return pTimeStamp;
            }
        }

		/// <summary>Returns a copy of the current instance.</summary>
        public new Packet Clone()
        {
            return new Packet(GetBytes());
        }

		/// <summary>Indicates if the packet contains any errors.</summary>
        public virtual bool IsValid
		{
			get
			{
				return false;
			}
		}

		/// <summary>Adds a boolean to the end of the packet.</summary>
		public void AddBoolean(bool value)
		{
			AddByte(value ? (byte)1 : (byte)0);
		}

		// Adds a byte value onto a packet
		/// <summary>Adds a single byte to the end of the packet.</summary>
		public void AddByte(byte value)
		{
			base.Add(value);
		}

		// Adds a byte value onto a packet
		/// <summary>Adds an array of bytes to the end of the packet.</summary>
		public void AddBytes(byte[] value)
		{
			foreach(byte item in value)
				base.Add(item);
		}

		// Adds a byte value onto a packet
		/// <summary>Adds a portion of an array of bytes to the end of the packet.</summary>
		public void AddBytes(byte[] value, int count)
		{
			for(int index = 0; index < count; index++)
				Add(value[index]);
		}

		// Adds a 16-bit number to a packet
		/// <summary>Adds a 2-byte number to the end of the packet.</summary>
		public void AddInt16(int value)
		{			
			AddBytes(BitConverter.GetBytes(Convert.ToInt16(value)));
		}

		// Adds a 32-bit integer onto a packet
		/// <summary>Adds a 4-byte number to the end of the packet.</summary>
		public void AddInt32(int value)
		{
			AddBytes(BitConverter.GetBytes(Convert.ToInt32(value)));
		}

        /// <summary>
        /// Compares the bytes of the current instance with the specified packet.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public bool EqualsArray(ArrayList value)
        {
            // Are the counts a match?
            if(Count != value.Count)
                return false;

            // Compare values
            for(int count = 0; count < value.Count; count++)
            {
                if (!this[count].Equals(value[count]))
                    return false;
            }

            // They match!
            return true;
        }

		// Adds a 32-bit integer onto a packet
		/// <summary>Adds a 2-byte unsigned integer to the end of the packet.</summary>
		public void AddWord(int value)
		{
			AddInt32(value);
		}

		/// <summary>Returns the entire packet as a byte array.</summary>
		public virtual byte[] GetBytes()
		{
            return (byte[])ToArray(typeof(byte));
		}

		/// <summary>Returns a string starting from the specified index.</summary>
		public string GetString(int index)
		{
			try
			{
				int Result = IndexOf((byte)0, index, Count - index - 1);
				return GetString(index, Result - index);

			}
			catch(ArgumentOutOfRangeException ex)
			{
				throw ex;
			}
		}

		/// <summary>Returns a string starting from the specified index.</summary>
		public string GetString(int index, int count)
		{
			try
			{
				// Is the count zero?
				if(count == 0)
					// Yes.  Return a blank string
					return "";
				// Convert the specified byte array to a string
				byte[] bytes = GetBytes();
				return ASCIIEncoding.ASCII.GetString(bytes, index, count).Trim().Replace("\0", "");
			}
			catch(ArgumentOutOfRangeException ex)
			{
				throw ex;
			}
		}

		// Adds a 32-bit floating point (Double) onto a packet
		/// <summary>Adds a 4-byte number to the end of the packet.</summary>
		public void AddFloat32(float value)
		{
			AddBytes(BitConverter.GetBytes(value));
		}

		/// <summary>Adds an 8-byte number to the end of the packet.</summary>
		public void AddFloat64(double value)
		{
			AddBytes(BitConverter.GetBytes(value));
		}

		// Adds a null-terminated string to a packet
		/// <summary>Adds a string to the end of the packet.</summary>
		/// <param name="value">A <strong>String</strong> to add to the end of the packet.</param>
		public void AddString(string value)
		{
			// Convert it to a byte array
			if(value != null)
				AddBytes(System.Text.ASCIIEncoding.ASCII.GetBytes(value));
			// And add a null termination
			AddByte(0);
		}

		// Adds a null-terminated string to a packet
		/// <summary>Adds a fixed-length string to the end of the packet.</summary>
		/// <param name="value">A <strong>String</strong> to add to the end of the packet.</param>
		/// <param name="maximumLength">An <strong>Integer</strong> specifying the length of the string.</param>
		public void AddString(string value, int maximumLength)
		{
			if(value.Length > maximumLength)
				value = value.Substring(0, maximumLength);
			// Convert it to a byte array
			AddBytes(System.Text.ASCIIEncoding.ASCII.GetBytes(value));
			// And add a null termination
			AddByte(0);
		}

		public override string ToString()
		{
			return ToString("G", CultureInfo.CurrentCulture);
		}

        public virtual string ToString(string format)
        {
            return ToString(format, CultureInfo.CurrentCulture);
        }

        public virtual string ToString(string format, IFormatProvider formatProvider)
        {
            CultureInfo culture = (CultureInfo)formatProvider;
            string Separator = culture.TextInfo.ListSeparator;
            // Which format was chosen?
            if (format == "B")
            {
                // If the packet is empty, exit
                if (Count == 0)
                    return "{}";
                // Output the bytes one by one
                StringBuilder Result = new StringBuilder();
                Result.Append("{");
                Result.Append(this[0].ToString());
                for (int i = 1; i < Count; i++)
                {
                    Result.Append(Separator);
                    Result.Append(this[i].ToString());
                }
                Result.Append("}");
                return Result.ToString();
            }
            else if (format == "G")
            {
                // Convert to a string
                byte[] Buffer = GetBytes();
                return ASCIIEncoding.ASCII.GetString(Buffer, 0, Buffer.Length);
            }
            else
            {
                return base.ToString();
            }
        }

		#endregion

        public static explicit operator Packet(byte[] bytes)
        {
            return new Packet(bytes);
        }

        public static explicit operator string(Packet packet)
        {
            return packet.ToString();
        }

        public static explicit operator byte[](Packet packet)
        {
            return packet.GetBytes();
        }
//
//        public static Packet operator +(Packet packet, byte value)
//        {
//            Packet Result = packet.Clone();
//            Result.AddByte(value);
//            return Result;
//        }
//
//        public static Packet operator +(Packet packet, Int16 value)
//        {
//            Packet Result = packet.Clone();
//            Result.AddInt16(value);
//            return Result;
//        }
//
//        public static Packet operator +(Packet packet, Int32 value)
//        {
//            Packet Result = packet.Clone();
//            Result.AddInt32(value);
//            return Result;
//        }
//
//        public static Packet operator +(Packet packet, float value)
//        {
//            Packet Result = packet.Clone();
//            Result.AddFloat32(value);
//            return Result;
//        }
//
//        public static Packet operator +(Packet packet, double value)
//        {
//            Packet Result = packet.Clone();
//            Result.AddFloat64(value);
//            return Result;
//        }
//
//        public static Packet operator +(Packet packet, string value)
//        {
//            Packet Result = packet.Clone();
//            Result.AddString(value);
//            return Result;
//        }
//
//        public static Packet operator +(Packet packet, byte[] value)
//        {
//            Packet Result = packet.Clone();
//            Result.AddBytes(value);
//            return Result;
//        }

		#region IFormattable Members

		string IFormattable.ToString(string format, IFormatProvider formatProvider)
		{
			return ToString(format, formatProvider);
		}

		#endregion

        #region IXmlSerializable Members

        System.Xml.Schema.XmlSchema IXmlSerializable.GetSchema()
        {
            throw new Exception("The method or operation is not implemented.");
        }

        void IXmlSerializable.ReadXml(XmlReader reader)
        {
            // Read in all the position objects
            reader.ReadStartElement();
            
#if Framework20
            byte[] Bytes = new byte[70];
            reader.ReadElementContentAsBase64(Bytes, 0, 65535);
            AddBytes(Bytes);
#endif
		}

        void IXmlSerializable.WriteXml(XmlWriter writer)
        {
            // Write the packet out as encoded bytes
            byte[] Bytes = GetBytes();
            writer.WriteBase64(Bytes, 0, Bytes.Length);
        }

        #endregion


#if !PocketPC || DesignTime
        #region ISerializable Members

        [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
        protected virtual void GetObjectData(SerializationInfo info, StreamingContext context)
        {
            info.AddValue("Bytes", GetBytes());
        }

        [SecurityPermission(SecurityAction.Demand, SerializationFormatter = true)]
        void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        {
            GetObjectData(info, context);
        }

        #endregion
#endif
	}

	public class PacketEventArgs : EventArgs
	{
		private Packet pPacket;

		public new static readonly PacketEventArgs Empty = new PacketEventArgs(null);

		public PacketEventArgs(Packet packet)
		{
			pPacket = packet;
		}

		public Packet Packet
		{
			get
			{
				return pPacket;
			}
		}
    }

	public delegate void PacketEventHandler(object sender, PacketEventArgs e);
}
